﻿module Floats

open Boxes

type FloatOffset = FloatOffset of int*int*int with
    (* Offset of the current box relative to the flow frame *)
    member this.addLeft dx = this.add dx 0 0
    member this.addRight dy = this.add 0 dy 0
    member this.addTop dy = this.add 0 0 dy
    member this.add (dl:int) dr dy =
        let (FloatOffset(l,r,y))=this
        FloatOffset(l+dl,r+dr,y+dy)

    member this.y =
         let (FloatOffset(l,r,yOffset)) = this
         yOffset

    member this.left =
         let (FloatOffset(l,r,yOffset)) = this
         l

    member this.right =
         let (FloatOffset(l,r,yOffset)) = this
         r


(* left and right floated blocks, placed relative to flow-frame *)
type Flow = Flow of PlacedLayout list * PlacedLayout list with

    member this.findSpace width y (loffset,roffset) availableWidth =
        (* find first vertical postion where there is room for a float *)
        let isInAction yOffset (((x,y),layout):PlacedLayout) =
            y <= yOffset && y + layout.size.height > yOffset

        let inAction yOffset (floats:PlacedLayout list) =
            floats |> List.filter (isInAction yOffset)

        let innerMost yOffset (floats:PlacedLayout list) =
            match inAction yOffset floats with 
            | [] -> None
            | lst -> 
                let found = lst |> List.maxBy (fun ((x,y),layout)->x+layout.size.width)
                let (x,y), layout = found
                Some((x+layout.size.width, y+layout.size.height))

        let rec findSpace1 y =            
            let (Flow(lefts,rights)) = this
            let left = innerMost y lefts
            let right = innerMost y rights
            match (left, right) with
            | (None, None) -> (y,0,0) (* no floats in action, so we place float here, regardless how wide it is *)
            | Some((floatIndentLeft,bottom)), None -> 
                    let leftIndent = if floatIndentLeft>loffset then floatIndentLeft-loffset else 0
                    if availableWidth - leftIndent > width then (y,leftIndent,0) else findSpace1 bottom    
            | None, Some((floatIndentRight,bottom)) -> 
                    let rightIndent = if floatIndentRight>roffset then floatIndentRight-roffset else 0
                    if availableWidth - rightIndent > width then (y,0,rightIndent) else findSpace1 bottom                
            | Some((floatIndentLeft,bottomLeft)), Some((floatIndentRight,bottomRight)) -> 
                    let leftIndent = if floatIndentLeft>loffset then floatIndentLeft-loffset else 0
                    let rightIndent = if floatIndentRight>roffset then floatIndentRight-roffset else 0
                    if availableWidth - leftIndent - rightIndent > width then (y,leftIndent,rightIndent) else findSpace1 (min bottomLeft bottomRight)

        // a float cannot be placed higher than a previous float, even if there is room
        let (Flow(lefts,rights)) = this
        let all = (lefts @ rights)
        let minY = match all with
                    | [] -> 0
                    | lst -> List.max [for ((x,y),layout) in lst -> y] 

        findSpace1 (max y minY)

    member this.addLeft (layout:Layout) (y:int) indents availableWidth=
        let y2, l, r = this.findSpace layout.size.width y indents availableWidth
        let (Flow(lefts,rights)) = this
        Flow(lefts @ [((l,y2), layout)], rights)

    member this.addRight (layout:Layout) (y:int) indents availableWidth =
        let y2, l, r = this.findSpace layout.size.width y indents availableWidth
        let (Flow(lefts,rights)) = this
        Flow(lefts, rights @ [((r,y2), layout)])

    member this.add isLeft (layout:Layout) (y:int) indents availableWidth = 
        if isLeft 
        then                     
            this.addLeft layout y indents availableWidth
        else 
            this.addRight layout y indents availableWidth

    member this.indents (offset:FloatOffset) =
        (* left and right indent relative to flow-frame for the current offset *)
        let max0 =
            function
            | [] -> 0
            | lst -> lst |> List.max  
        
        let leftIndent (offset:FloatOffset) =
            let leftIndent1 (FloatOffset(l,r,yOffset)) ((x,y),ElementBoxLayout(BoxSize(width,height), elem, children)) =
                if y <= yOffset && y + height > yOffset then Some(x + width) else None

            let (Flow(leftFloats, rightFloats)) = this  
            leftFloats |> List.choose (leftIndent1 offset) |> max0

        let rightIndent (offset:FloatOffset) =
            let rightIndent1 (FloatOffset(l,r,yOffset)) ((x,y),ElementBoxLayout(BoxSize(width,height), elem, children)) =
                if y <= yOffset && y + height > yOffset then Some(x + width) else None 

            let (Flow(leftFloats, rightFloats)) = this  
            rightFloats |> List.choose (rightIndent1 offset) |> max0
        
        (leftIndent offset), (rightIndent offset)

    member this.calculateAvailableLineWidth (offset:FloatOffset) =
        (* calculate the available line width, when taking floats into account *)
        let (FloatOffset(l,r,y)) = offset
        let (leftIndent, rightIndent) = this.indents offset
        r - l - leftIndent - rightIndent







